import calendar
import tkinter as tk
from datetime import datetime, time
from tkinter import ttk

import pandas as pd


def get_workday_status():
    """
    通过GUI获取用户输入的年月和1-20日工作日状态
    :return: workday_status 工作日状态列表
    """
    # 获取当前日期作为默认值
    today = datetime.today()
    default_year = today.year
    default_month = today.month

    # 创建年月选择窗口
    year_month_root = tk.Tk()
    year_month_root.title("选择年月")
    year_month_root.geometry("300x150")

    # 居中框架
    ym_frame = ttk.Frame(year_month_root, padding="20")
    ym_frame.place(relx=0.5, rely=0.5, anchor="center")

    # 年份选择 (默认前后5年)
    ttk.Label(ym_frame, text="年份:").grid(row=0, column=0, sticky="w", pady=5)
    year_var = tk.StringVar(value=str(default_year))
    year_spinbox = ttk.Spinbox(ym_frame, from_=default_year - 10, to=default_year, textvariable=year_var, width=10)
    year_spinbox.grid(row=0, column=1, pady=5, padx=(10, 0))

    # 月份选择
    ttk.Label(ym_frame, text="月份:").grid(row=1, column=0, sticky="w", pady=5)
    month_var = tk.StringVar(value=str(default_month))
    month_spinbox = ttk.Spinbox(ym_frame, from_=1, to=12, textvariable=month_var, width=10)
    month_spinbox.grid(row=1, column=1, pady=5, padx=(10, 0))

    def confirm_year_month():
        year_month_root.quit()
        year_month_root.destroy()

    # 确认按钮
    ttk.Button(ym_frame, text="确认", command=confirm_year_month).grid(row=2, column=0, columnspan=2, pady=10)

    year_month_root.mainloop()

    # 获取用户选择的年月
    year = int(year_var.get())
    month = int(month_var.get())

    # 初始化20天的工作日状态，自动将周末设置为休息日
    workday_status = []
    for day in range(1, 21):  # 1-20日
        # 获取该日期是星期几 (0=Monday, 6=Sunday)
        day_of_week = calendar.weekday(year, month, day)
        # 周末(周六=5, 周日=6)设置为休息日(False)，工作日设置为工作日(True)
        workday_status.append(day_of_week not in [5, 6])  # 周末为休息日

    root = tk.Tk()
    root.title("工作日设置")
    root.geometry("600x380")

    # 获取当月1日是星期几（0=Monday, 6=Sunday）
    first_weekday = calendar.monthrange(year, month)[0]

    # 创建主框架并居中
    main_frame = ttk.Frame(root, padding="20")
    main_frame.place(relx=0.5, rely=0.5, anchor="center")

    # 创建标题
    title_label = ttk.Label(main_frame, text=f"请设置{year}年{month}月1-20日的工作日状态", font=("Arial", 14, "bold"))
    title_label.grid(row=0, column=0, columnspan=7, pady=(0, 10))

    # 创建按钮列表
    buttons = []
    button_vars = []

    # 创建自定义样式
    style = ttk.Style()
    style.configure("Workday.TButton", background="#90EE90", foreground="black")
    style.configure("Restday.TButton", background="#FFB6C1", foreground="black")

    def toggle_day(day_index):
        """切换指定日期的状态"""
        # 切换变量状态
        button_vars[day_index].set(not button_vars[day_index].get())
        current_state = button_vars[day_index].get()

        # 更新按钮显示
        button = buttons[day_index]
        if current_state:
            button.config(text=f"{day_index + 1}\n工作日", style="Workday.TButton")
        else:
            button.config(text=f"{day_index + 1}\n休息日", style="Restday.TButton")

    # 创建日历式按钮布局（一周7天）
    weekdays = ['一', '二', '三', '四', '五', '六', '日']
    for i, weekday in enumerate(weekdays):
        label = ttk.Label(main_frame, text=weekday, font=("Arial", 10, "bold"))
        label.grid(row=1, column=i, padx=5, pady=(0, 5))

    # 创建20个日期按钮，按当月日历方式排列
    for i in range(20):
        var = tk.BooleanVar(value=workday_status[i])  # 使用自动设置的状态
        button_style = "Workday.TButton" if workday_status[i] else "Restday.TButton"
        button_text = f"{i + 1}\n工作日" if workday_status[i] else f"{i + 1}\n休息日"

        button = ttk.Button(main_frame, text=button_text, width=8, command=lambda idx=i: toggle_day(idx),
                            style=button_style)

        # 计算在日历中的位置
        # first_weekday 是当月1号是星期几 (0=Monday)
        # i 是日期索引 (0-19 对应 1-20号)
        position = first_weekday + i  # 从1号开始的位置
        row = 2 + position // 7  # 第几行（从第2行开始）
        col = position % 7  # 第几列

        button.grid(row=row, column=col, padx=3, pady=3)

        button_vars.append(var)
        buttons.append(button)

    # 创建操作按钮框架
    action_frame = ttk.Frame(main_frame)
    action_frame.grid(row=6, column=0, columnspan=7, pady=15)

    def submit_selection():
        """提交选择"""
        for i in range(20):
            workday_status[i] = button_vars[i].get()
        root.quit()
        root.destroy()

    # 创建操作按钮
    ttk.Button(action_frame, text="确认提交", command=submit_selection).pack()

    # 说明文本
    help_text = "点击日期按钮切换工作日/休息日状态\n工作日显示为绿色，休息日显示为粉色"
    help_label = ttk.Label(main_frame, text=help_text, font=("Arial", 9))
    help_label.grid(row=7, column=0, columnspan=7, pady=(5, 0))

    root.mainloop()

    return year, month, workday_status


def parse_and_categorize_punch_times(cell_value, is_workday):
    """
    解析单元格中的打卡时间字符串，并根据时间范围将其分类为四个固定时间段的打卡时间
    :param cell_value: 包含多个打卡时间的字符串
    :param is_workday: 是否为工作日
    :return: 上午上班打卡时间, 上午下班打卡时间, 下午上班打卡时间, 下午下班打卡时间
    """
    # 初始化返回值
    morning_start_time = None
    morning_end_time = None
    afternoon_start_time = None
    afternoon_end_time = None

    # 解析打卡时间字符串
    if not cell_value or str(cell_value).strip() == '':
        return [morning_start_time, morning_end_time, afternoon_start_time, afternoon_end_time]

    # 按行分割，去除空行
    times = [t.strip() for t in str(cell_value).split('\n') if t.strip()]
    # 转换为时间对象
    time_objects = []
    for t in times:
        if ':' in t:
            time_obj = datetime.strptime(t, '%H:%M').time()
            time_objects.append(time_obj)

    # 定义时间范围
    if is_workday:
        afternoon_end_start = time(18, 0)  # 工作日下班时间
    else:
        afternoon_end_start = time(17, 30)  # 非工作日下班时间

    # 遍历打卡时间并分类
    for t in time_objects:
        if time(4, 0) <= t < time(12, 0):
            # 04:00~12:00 时间段
            if morning_start_time is None:
                morning_start_time = t
            else:
                morning_end_time = t
        elif time(12, 0) <= t < time(13, 30):
            # 12:00~13:30 时间段
            morning_end_time = t
        elif time(13, 30) <= t < afternoon_end_start:
            # 13:30~下班时间前
            if afternoon_start_time is None:
                afternoon_start_time = t
        elif afternoon_end_start <= t or t < time(4, 0):
            # 下班时间后到次日04:00前
            afternoon_end_time = t

    return morning_start_time, morning_end_time, afternoon_start_time, afternoon_end_time


def calculate_overtime_for_time(actual_time, standard_time, year, month, day):
    """
    计算单个时间点的加班情况
    :param actual_time: 实际时间
    :param standard_time: 标准时间
    :param year: 年份
    :param month: 月份
    :param day: 日期
    :return: 加班信息或None
    """

    # 计算实际下班时间
    if actual_time < time(4, 0):
        # 凌晨4点前，打卡时间应该是第二天凌晨
        actual_datetime = datetime(year, month, day + 1, actual_time.hour, actual_time.minute)
    else:
        # 正常情况
        actual_datetime = datetime(year, month, day, actual_time.hour, actual_time.minute)

    # 计算标准下班时间
    standard_datetime = datetime(year, month, day, standard_time.hour, standard_time.minute)

    # 计算时间差
    time_diff = actual_datetime - standard_datetime
    overtime_hours = time_diff.total_seconds() / 3600

    # 检查是否算加班
    if overtime_hours >= 1.0:
        return (standard_datetime.strftime('%Y-%m-%d %H:%M'),  # 标准下班时间
                actual_datetime.strftime('%Y-%m-%d %H:%M'),  # 实际下班时间
                round(overtime_hours / 8, 4)  # 更精确的小数位数
                )

    return None


def check_overtime(morning_end_time, afternoon_end_time, is_workday, day, month, year):
    """
    详细检查加班情况
    :param morning_end_time: 上午下班打卡时间
    :param afternoon_end_time: 下午下班打卡时间
    :param is_workday: 是否为工作日
    :param day: 日期
    :param month: 月份
    :param year: 年份
    :return: 加班信息列表
    """

    overtime_list = []

    # 获取上午下班标准时间
    standard_morning_end_time = time(12, 0)
    # 获取下午下班标准时间
    standard_afternoon_end_time = time(18, 0) if is_workday else time(17, 30)

    # 检查上午下班是否加班
    if morning_end_time:
        overtime_info = calculate_overtime_for_time(morning_end_time, standard_morning_end_time, year, month, day)
        if overtime_info:
            overtime_list.append(overtime_info + ("150%" if is_workday else "200%",))

    # 检查下午下班是否加班
    if afternoon_end_time:
        overtime_info = calculate_overtime_for_time(afternoon_end_time, standard_afternoon_end_time, year, month, day)
        if overtime_info:
            overtime_list.append(overtime_info + ("150%" if is_workday else "200%",))

    return overtime_list


def check_abnormal_punch(morning_start_time, afternoon_start_time, is_workday, day, month, year):
    """
    检查异常打卡情况（更新版）
    :param morning_start_time: 上午上班打卡时间
    :param afternoon_start_time: 下午上班打卡时间
    :param is_workday: 是否为工作日
    :param day: 日期
    :param month: 月份
    :param year: 年份
    :return: 异常打卡列表
    """

    abnormal_list = []

    # 获取上午上班标准时间
    standard_morning_start_time = time(8, 30) if is_workday else time(9, 0)
    # 获取下午上班标准时间
    standard_afternoon_start_time = time(14, 0)

    if morning_start_time:
        if morning_start_time > standard_morning_start_time:
            actual_morning_start_datetime = datetime(year, month, day, morning_start_time.hour,
                                                     morning_start_time.minute)
            abnormal_list.append(actual_morning_start_datetime.strftime('%Y/%m/%d %H:%M'))
    else:
        abnormal_list.append(f"{year}/{month}/{day} 上午未打卡")
    if afternoon_start_time:
        if afternoon_start_time > standard_afternoon_start_time:
            actual_afternoon_start_datetime = datetime(year, month, day, afternoon_start_time.hour,
                                                       afternoon_start_time.minute)
            abnormal_list.append(actual_afternoon_start_datetime.strftime('%Y/%m/%d %H:%M'))
    else:
        abnormal_list.append(f"{year}/{month}/{day} 下午未打卡")

    return abnormal_list


def process_attendance():
    """
    处理考勤表自动填写
    """
    # 获取工作日状态
    year, month, workday_status = get_workday_status()
    print(f"{year}年{month}月工作日状态已设置为：{workday_status}")

    # 读取填写表中的员工月考勤表，获取员工列表
    attendance_df = pd.read_excel('填写表.xlsx', sheet_name='1-员工月考勤表', header=4)
    employees = attendance_df['姓名'].dropna().tolist()
    print(f"已获取员工列表：{employees}")

    # 读取打卡表
    punch_df = pd.read_excel('打卡表.xlsx', sheet_name='打卡时间', header=0)

    # 设置打卡表索引为员工姓名，便于查找
    punch_df.set_index('姓名', inplace=True)

    # 读取现有的加班登记表和异常统计表
    attendance_template_df = pd.read_excel('填写表.xlsx', sheet_name='1-员工月考勤表', header=None)
    overtime_template_df = pd.read_excel('填写表.xlsx', sheet_name='2-员工加班登记表', header=None)
    abnormal_template_df = pd.read_excel('填写表.xlsx', sheet_name='4-打卡异常统计表', header=None)

    # 准备新的数据列表
    overtime_data = []  # 加班数据记录
    abnormal_data = []  # 异常打卡记录

    # 处理
    print("正在处理...")
    for i, employee_name in enumerate(employees):
        print(f"处理员工 {i + 1}/{len(employees)}: {employee_name}")

        employee_punch_data = punch_df.loc[employee_name]

        # 处理每天的打卡记录（B~U列对应1日至20日）
        for day in range(1, 21):
            # 获取对应列的数据
            cell_value = employee_punch_data.iloc[day - 1]

            # 确定是否为工作日
            is_workday = workday_status[day - 1]

            # 解析打卡时间
            morning_start_time, morning_end_time, afternoon_start_time, afternoon_end_time = parse_and_categorize_punch_times(
                cell_value, is_workday)
            punch_times_list = [morning_start_time, morning_end_time, afternoon_start_time, afternoon_end_time]

            # 先检查是否有打卡记录
            has_punch_record = any(time is not None for time in punch_times_list)

            if not has_punch_record:
                # 没有打卡记录，直接判断是事假还是休息
                day_status = "事假" if is_workday else "休息"
                print(f"  已解析第{day}日打卡记录：{day_status}，{punch_times_list}")

                # # 找到员工在考勤表中的行索引
                # employee_row_idx = attendance_df[attendance_df['姓名'] == employee_name].index[0]
                # # 更新考勤状态
                # attendance_df.iloc[employee_row_idx, day + 1] = day_status

                continue  # 跳过加班和异常检查

            # 检查是否有加班
            overtime_list = check_overtime(morning_end_time, afternoon_end_time, is_workday, day, month, year)

            # 确定考勤状态
            day_status = "加班" if overtime_list else "出勤"
            print(f"  已解析第{day}日打卡记录：{day_status}，{punch_times_list}")

            # # 找到员工在考勤表中的行索引
            # employee_row_idx = attendance_df[attendance_df['姓名'] == employee_name].index[0]
            # # 更新考勤状态
            # attendance_df.iloc[employee_row_idx, day + 1] = day_status

            # 加入加班数据记录中
            for overtime_info in overtime_list:
                # 在前面加上员工姓名
                overtime_data.append((employee_name,) + overtime_info)

            # 检查是否有异常
            abnormal_list = check_abnormal_punch(morning_start_time, afternoon_start_time, is_workday, day, month, year)

            # 加入异常数据记录中
            for abnormal_info in abnormal_list:
                # 在前面加上员工姓名
                abnormal_data.append((employee_name,) + (abnormal_info,))

    print("处理完成！")

    print("加班数据记录：")
    for record in overtime_data:
        print(f"  {record}")

    print("异常数据记录：")
    for record in abnormal_data:
        print(f"  {record}")


# 使用示例
if __name__ == "__main__":
    process_attendance()
